<?php

namespace Ktnw\sms\Services;

use Exception;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Ktnw\sms\utils\Config;

/**
 * 发送短信相关业务类
 */
class SmsBaseService
{

    /**
     * 校验是否可以发送短信
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     * @throws Exception
     */
    public static function checkSendStatus(string $phone, string $msgType): bool
    {
        if (empty($phone) || empty($msgType)) {
            return false;
        }
        $second = Config::getConfigValue("smsConfig.send_interval_time");
        $second = empty($second) ? 60 : intval($second);
        // 若未记录短信发送日志
        if (!Config::getConfigValue("smsConfig.save_sms_log", false)) {
            $lastSms = self::findLastSmsFromCache($phone, $msgType);
            if (empty($lastSms)) {
                throw new Exception("短信发送间隔时间为{$second}秒，间隔时间未到，不能发送。");
            }
        }

        // 若已记录短信发送日志 执行以下流程
        $lastSms = self::findLastSms($phone, $msgType);
        if (empty($lastSms)) {
            return true;
        }

        // 上次短信发送时间
        $preSendTime = strtotime($lastSms['created_at']);
        if ($preSendTime > 0 && ($preSendTime + $second) > time()) {
            throw new Exception("短信发送间隔时间为{$second}秒，间隔时间未到，不能发送。");
        }
        return true;
    }


    /**
     * 记录发送短信日志
     *
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     * @param mixed $smsPrams 发送短信的参数
     * @param bool $toDB 是否保存到数据库 true-是; false-仅缓存
     * @param array $otherParams 短信日志的其他参数
     * @param string $smsForm 来源
     * @return bool
     */
    public static function saveSmsLog(string $phone, string $msgType, $smsPrams, bool $toDB = true, array $otherParams = [], string $smsForm = ''): bool
    {
        try {
            $smsPrams         = is_object($smsPrams) ? self::objectToArray($smsPrams) : $smsPrams;
            $smsPrams         = is_array($smsPrams) ? json_encode($smsPrams, JSON_UNESCAPED_UNICODE) : $smsPrams;
            $smsEffectiveTime = Config::getConfigValue("smsConfig.sms_effective_time");
            $smsEffectiveTime = empty($smsEffectiveTime) ? 10 : intval($smsEffectiveTime);
            $second           = time() + $smsEffectiveTime * 60;
            $smsLog           = [
                "phone"          => $phone, "send_status" => 'Y', 'sms_template' => $msgType, 'content' => $smsPrams,
                "send_from"      => $smsForm, "created_at" => date('Y-m-d H:i:s', time()),
                "valid_end_time" => date('Y-m-d H:i:s', $second)
            ];
            if ($toDB) {
                $id = DB::table(self::getSmsLogTableName())->insertGetId(array_merge($smsLog, $otherParams));
                self::findLastSms($phone, $msgType, true);
            } else {
                self::cacheSmsLog($smsLog, $phone, $msgType);
            }
            return true;
        } catch (Exception $e) {
            Log::error($e->getMessage());
            return false;
        }
    }

    /**
     * 校验短信验证码
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     * @param string $smsCode 短信验证码
     * @param bool $once 校验通过后是否失效 true-是;false-否;
     * @throws Exception
     */
    public static function checkSmsCode(string $phone, string $msgType, string $smsCode, bool $once = true): bool
    {
        if (empty($smsCode)) {
            throw new Exception("请输入短信验证码");
        }
        if (empty($phone) || empty($msgType)) {
            throw new Exception("接收短信验证码的手机号码不能空");
        }
        $lastSms = self::findLastSms($phone, $msgType);
        if (empty($lastSms)) {
            throw new Exception("您的短信验证码已失效，请重新发送。");
        }
        if ($smsCode != $lastSms['content']) {
            throw new Exception("短信验证码错误");
        }

        // 验证短信验证码是否过期
        $validEndTime = strtotime($lastSms["valid_end_time"]);
        if (time() > $validEndTime) {
            throw new Exception("短信验证码已过期，请重新获取。");
        }

        if ($once) {
            // 使短信验证码失效
            self::invalidSmsCode($phone, $msgType);
        }
        return true;
    }

    /**
     * 短信失效
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     */
    public static function invalidSmsCode(string $phone, string $msgType)
    {
        $lastSms = self::findLastSms($phone, $msgType);
        if ($lastSms) {
            DB::table(self::getSmsLogTableName())->where("id", $lastSms["id"])->update(["valid_end_time" => self::getCurTimeStr(), "use_status" => 2]);
        }
        self::removeCacheSmsLog($phone, $msgType);
    }


    /**
     * 查询最后一条短信日志
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     * @param bool $fromDB 是否强制从数据库中查询 true-是; false-否;
     */
    protected static function findLastSms(string $phone, string $msgType, bool $fromDB = false)
    {
        $smsInfo = [];
        if (!$fromDB) {
            $smsInfo = self::findLastSmsFromCache($phone, $msgType);
        }
        if (empty($smsInfo)) {
            // 从数据库中查询
            $smsInfo = self::findLastSmsFromDB($phone, $msgType);
            // 缓存短信日志
            self::cacheSmsLog($smsInfo, $phone, $msgType);
        }
        return $smsInfo;
    }

    /**
     * 缓存日志
     * @param array $smsInfo 日志记录信息
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     */
    protected static function cacheSmsLog(array $smsInfo, string $phone, string $msgType)
    {
        if (empty($smsInfo)) {
            return;
        }
        // 缓存到redis中
        $cacheKey  = self::getSmsLogCacheKey($phone, $msgType);
        $cacheTime = Config::getConfigValue("smsConfig.sms_effective_time");
        Cache::put($cacheKey, $smsInfo, intval($cacheTime) * 60);
    }

    /**
     * 删除缓存中的短信日志
     * @param string $phone
     * @param string $msgType
     */
    protected static function removeCacheSmsLog(string $phone, string $msgType)
    {
        // 缓存到redis中
        $cacheKey = self::getSmsLogCacheKey($phone, $msgType);
        Cache::forget($cacheKey);
    }

    /**
     * 从缓存中获取短信日志
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     */
    protected static function findLastSmsFromCache(string $phone, string $msgType)
    {
        $cacheKey = self::getSmsLogCacheKey($phone, $msgType);
        return Cache::has($cacheKey) ? Cache::get($cacheKey) : [];
    }

    /**
     * 获取短信日志缓存的key
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     * @return string
     */
    protected static function getSmsLogCacheKey(string $phone, string $msgType): string
    {
        return Config::getConfigValue("smsConfig.last_sms_cache_key") . $msgType . '_' . $phone;
    }

    /**
     * 获取记录短信日志的table name
     * @return string
     */
    private static function getSmsLogTableName(): string
    {
        return Config::getConfigValue("smsConfig.sms_log_table_name");
    }

    /**
     * 查询最后一条短信日志
     * @param string $phone 手机号
     * @param string $msgType 短信模板
     */
    private static function findLastSmsFromDB(string $phone, string $msgType): array
    {
        $where[] = ['phone', '=', $phone];
        if (!empty($msgType)) {
            $where[] = ['sms_template', '=', $msgType];
        }
        $r = DB::table(self::getSmsLogTableName())->where($where)->orderBy('id', "DESC")->first();
        return self::objectToArray($r);
    }

    /**
     * 对象转换数组
     * @param $obj
     * @return array
     */
    private static function objectToArray($obj): array
    {
        if (empty($obj)) {
            return [];
        }
        if (!is_array($obj) && !is_object($obj)) {
            return $obj;
        }
        $_arr = is_object($obj) ? get_object_vars($obj) : $obj;
        $arr  = [];
        foreach ($_arr as $key => $val) {
            $val       = (is_array($val)) || is_object($val) ? self::objectToArray($val) : $val;
            $arr[$key] = $val;
        }
        return $arr;
    }

    /**
     * 获取当前日期的时间字符串
     */
    private static function getCurTimeStr()
    {
        return date("Y-m-d H:i:s", time());
    }

}
